﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Vasuleg.UIDrive
{
    public static class ExpressionUtils
    {
        public class ExpressionPathNode : MemberPathNode
        {
            private readonly MemberExpression _memberExpression;
            private Action<object> _setDelegate;

            internal ExpressionPathNode(object target, ReadOnlyCollection<MemberInfo> memberPath,
                MemberExpression expression) : base(target, memberPath)
            {
                _memberExpression = expression;
            }

            public override string ToString()
            {
                return new StringBuilder()
                    .Append(Target)
                    .Append('.')
                    .AppendJoin('.', MemberPath.Select(member => member.Name))
                    .ToString();
            }

            public override void Set(object value)
            {
                if (_setDelegate == null)
                {
                    var paramExp = Expression.Parameter(typeof(object));
                    Expression expression = Expression.Convert(paramExp, _memberExpression.Type);
                    expression = Expression.Assign(_memberExpression, expression);
                    _setDelegate = Expression.Lambda<Action<object>>(expression, paramExp).Compile();
                }

                _setDelegate(value);
            }
        }


        public static IEnumerable<MemberPathNode> SelectMemberPath(Expression expression, object checkObj = null)
        {
            var memberList = new List<MemberExpression>();
            SelectMemberPath(expression, memberList, checkObj);
            if (memberList.Count == 0)
            {
                return Array.Empty<MemberPathNode>();
            }
            var pathNodeList = new List<MemberPathNode>();
            foreach (var memberExpression in memberList)
            {
                var memberInfoPath = new List<MemberInfo>();
                var tempMe = memberExpression;
                while (tempMe.Expression is MemberExpression newMe)
                {
                    memberInfoPath.Add(tempMe.Member);
                    tempMe = newMe;
                }

                if (!(tempMe.Expression is ConstantExpression coe))
                {
                    continue;
                }

                var lastMember = tempMe.Member;
                var target = coe.Value;
                var targetType = target.GetType();

                //处理掉闭包的问题
                if (targetType.IsSealed 
                    && targetType.IsNested
                    && targetType.Name.Contains("c__DisplayClass")
                    && lastMember is FieldInfo fieldInfo)
                {
                    target = fieldInfo.GetValue(target);
                }
                else
                {
                    memberInfoPath.Add(lastMember);
                }
                memberInfoPath.Reverse();


                if (!pathNodeList.Any(pathNode => pathNode.Target == target
                                                  && pathNode.MemberPath.SequenceEqual(memberInfoPath)))
                {
                    pathNodeList.Add(new ExpressionPathNode(target, memberInfoPath.AsReadOnly(), memberExpression));
                }
            }


            return pathNodeList;
        }

        private static List<MemberExpression> SelectMemberPath(Expression expression,
            List<MemberExpression> memberInfoList, object checkObj)
        {
            switch (expression)
            {
                case LambdaExpression le:
                    return SelectMemberPath(le.Body, memberInfoList, checkObj);
                case BinaryExpression be:
                    SelectMemberPath(be.Left, memberInfoList, checkObj);
                    SelectMemberPath(be.Right, memberInfoList, checkObj);
                    return memberInfoList;
                case MethodCallExpression mce:
                    foreach (var argument in mce.Arguments)
                    {
                        SelectMemberPath(argument, memberInfoList, checkObj);
                    }

                    return SelectMemberPath(mce.Object, memberInfoList, checkObj);
                case MemberExpression me:
                    var tempMe = me;
                    while (tempMe.Expression is MemberExpression newMe)
                    {
                        tempMe = newMe;
                    }

                    if (!(tempMe.Expression is ConstantExpression coe))
                    {
                        return SelectMemberPath(tempMe.Expression, memberInfoList, checkObj);
                    }

                    if (checkObj == null || coe.Value == checkObj)
                    {
                        memberInfoList.Add(me);
                    }

                    return memberInfoList;
                case ConditionalExpression ce:
                    SelectMemberPath(ce.Test, memberInfoList, checkObj);
                    SelectMemberPath(ce.IfTrue, memberInfoList, checkObj);
                    SelectMemberPath(ce.IfFalse, memberInfoList, checkObj);
                    return memberInfoList;
                case UnaryExpression ue:
                    return SelectMemberPath(ue.Operand, memberInfoList, checkObj);
            }

            return memberInfoList;
        }
    }
}